package gssapi

import (
	
	
	
	
	
	

	
	
	
)

// RFC 4121, section 4.2.6.1

const (
	// MICTokenFlagSentByAcceptor - this flag indicates the sender is the context acceptor.  When not set, it indicates the sender is the context initiator
	MICTokenFlagSentByAcceptor = 1 << iota
	// MICTokenFlagSealed - this flag indicates confidentiality is provided for.  It SHALL NOT be set in MIC tokens
	MICTokenFlagSealed
	// MICTokenFlagAcceptorSubkey - a subkey asserted by the context acceptor is used to protect the message
	MICTokenFlagAcceptorSubkey
)

const (
	micHdrLen = 16 // Length of the MIC Token's header
)

// MICToken represents a GSS API MIC token, as defined in RFC 4121.
// It contains the header fields, the payload (this is not transmitted) and
// the checksum, and provides the logic for converting to/from bytes plus
// computing and verifying checksums
type MICToken struct {
	// const GSS Token ID: 0x0404
	Flags byte // contains three flags: acceptor, sealed, acceptor subkey
	// const Filler: 0xFF 0xFF 0xFF 0xFF 0xFF
	SndSeqNum uint64 // sender's sequence number. big-endian
	Payload   []byte // your data! :)
	Checksum  []byte // checksum of { payload | header }
}

// Return the 2 bytes identifying a GSS API MIC token
func getGSSMICTokenID() *[2]byte {
	return &[2]byte{0x04, 0x04}
}

// Return the filler bytes used in header
func fillerBytes() *[5]byte {
	return &[5]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
}

// Marshal the MICToken into a byte slice.
// The payload should have been set and the checksum computed, otherwise an error is returned.
func ( *MICToken) () ([]byte, error) {
	if .Checksum == nil {
		return nil, errors.New("checksum has not been set")
	}

	 := make([]byte, micHdrLen+len(.Checksum))
	copy([0:micHdrLen], .getMICChecksumHeader()[:])
	copy([micHdrLen:], .Checksum)

	return , nil
}

// SetChecksum uses the passed encryption key and key usage to compute the checksum over the payload and
// the header, and sets the Checksum field of this MICToken.
// If the payload has not been set or the checksum has already been set, an error is returned.
func ( *MICToken) ( types.EncryptionKey,  uint32) error {
	if .Checksum != nil {
		return errors.New("checksum has already been computed")
	}
	,  := .checksum(, )
	if  != nil {
		return 
	}
	.Checksum = 
	return nil
}

// Compute and return the checksum of this token, computed using the passed key and key usage.
// Note: This will NOT update the struct's Checksum field.
func ( *MICToken) ( types.EncryptionKey,  uint32) ([]byte, error) {
	if .Payload == nil {
		return nil, errors.New("cannot compute checksum with uninitialized payload")
	}
	 := make([]byte, micHdrLen+len(.Payload))
	copy([0:], .Payload)
	copy([len(.Payload):], .getMICChecksumHeader())

	,  := crypto.GetEtype(.KeyType)
	if  != nil {
		return nil, 
	}
	return .GetChecksumHash(.KeyValue, , )
}

// Build a header suitable for a checksum computation
func ( *MICToken) () []byte {
	 := make([]byte, micHdrLen)
	copy([0:2], getGSSMICTokenID()[:])
	[2] = .Flags
	copy([3:8], fillerBytes()[:])
	binary.BigEndian.PutUint64([8:16], .SndSeqNum)
	return 
}

// Verify computes the token's checksum with the provided key and usage,
// and compares it to the checksum present in the token.
// In case of any failure, (false, err) is returned, with err an explanatory error.
func ( *MICToken) ( types.EncryptionKey,  uint32) (bool, error) {
	,  := .checksum(, )
	if  != nil {
		return false, 
	}
	if !hmac.Equal(, .Checksum) {
		return false, fmt.Errorf(
			"checksum mismatch. Computed: %s, Contained in token: %s",
			hex.EncodeToString(), hex.EncodeToString(.Checksum))
	}
	return true, nil
}

// Unmarshal bytes into the corresponding MICToken.
// If expectFromAcceptor is true we expect the token to have been emitted by the gss acceptor,
// and will check the according flag, returning an error if the token does not match the expectation.
func ( *MICToken) ( []byte,  bool) error {
	if len() < micHdrLen {
		return errors.New("bytes shorter than header length")
	}
	if !bytes.Equal(getGSSMICTokenID()[:], [0:2]) {
		return fmt.Errorf("wrong Token ID, Expected %s, was %s",
			hex.EncodeToString(getGSSMICTokenID()[:]),
			hex.EncodeToString([0:2]))
	}
	 := [2]
	 := &MICTokenFlagSentByAcceptor != 0
	if  && ! {
		return errors.New("unexpected acceptor flag is set: not expecting a token from the acceptor")
	}
	if ! &&  {
		return errors.New("unexpected acceptor flag is not set: expecting a token from the acceptor, not in the initiator")
	}
	if !bytes.Equal([3:8], fillerBytes()[:]) {
		return fmt.Errorf("unexpected filler bytes: expecting %s, was %s",
			hex.EncodeToString(fillerBytes()[:]),
			hex.EncodeToString([3:8]))
	}

	.Flags = 
	.SndSeqNum = binary.BigEndian.Uint64([8:16])
	.Checksum = [micHdrLen:]
	return nil
}

// NewInitiatorMICToken builds a new initiator token (acceptor flag will be set to 0) and computes the authenticated checksum.
// Other flags are set to 0.
// Note that in certain circumstances you may need to provide a sequence number that has been defined earlier.
// This is currently not supported.
func ( []byte,  types.EncryptionKey) (*MICToken, error) {
	 := MICToken{
		Flags:     0x00,
		SndSeqNum: 0,
		Payload:   ,
	}

	if  := .SetChecksum(, keyusage.GSSAPI_INITIATOR_SIGN);  != nil {
		return nil, 
	}

	return &, nil
}